﻿using Minio.DataModel;
using Minio.Extend;
using Minio.Extend.Db;
using Models.MinioFile;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Basic.Service
{
    public class MinioFileService
    {

        public static string bucketName = Appsettings.App("SystempreFix");//"asiatrip";
        private readonly RedisHelper _redisHelper;
        private readonly MinioAPI _minioAPI;

        public MinioFileService(RedisHelper redisHelper,
            MinioAPI minioAPI)
        {
            this._redisHelper = redisHelper;
            this._minioAPI = minioAPI;
        }

        /// <summary>
        /// 获取文件信息
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="totalSize"></param>
        public FileInfoModel GetFileInfo(string identifier, string fileName, long totalSize)
        {
            // 根据MD5查询数据库此文件是否已上传成功
            // 成功则直接秒传
            using (MEDbContext db = new MEDbContext())
            {
                // 验证该MD5是否存在
                var entites = db.SystemFileBusinessEntities.Where(x => x.ObjectName == identifier && x.BucketName == bucketName);
                if (!entites.Any())
                    return null;
                // 验证文件名是否发生过变更
                return FileUpdate(identifier, fileName, totalSize, db);
            }
        }

        /// <summary>
        /// 文件是否需要更新
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="fileName"></param>
        /// <param name="totalSize"></param>
        /// <param name="db"></param>
        /// <returns></returns>
        private static FileInfoModel FileUpdate(string identifier, string fileName, long totalSize, MEDbContext db)
        {
            string title = Path.GetFileNameWithoutExtension(fileName);
            string extension = Path.GetExtension(fileName);
            var entity = db.Set<SystemFileBusinessEntity>().FirstOrDefault(x => x.BucketName == bucketName && x.ObjectName == identifier && x.Title == title && x.Extension == extension);
            if (entity != null)
            {
                return new FileInfoModel
                {
                    Id = entity.Id,
                    NeedMerge = false,
                    SkipUpload = true,
                    FileName = fileName,
                };
            }
            else
            {
                entity = db.Set<SystemFileBusinessEntity>().Add(new SystemFileBusinessEntity
                {
                    BucketName = bucketName,
                    CreateTime = DateTime.Now,
                    Extension = extension,
                    ObjectName = identifier,
                    Title = title,
                    UpdateTime = DateTime.Now,
                    Size = totalSize
                }).Entity;
                db.SaveChanges();
                return new FileInfoModel
                {
                    Id = entity.Id,
                    NeedMerge = false,
                    SkipUpload = true,
                    FileName = fileName,
                };
            }
        }

        /// <summary>
        /// 查询分片信息
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="totalSize"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public async Task<int[]> GePartInfoAsync(string identifier, long totalSize, string fileName)
        {
            // Redis查询已上传分片,返回未上传分片
            var basicInfoIsExist = await _redisHelper.HashExistsAsync("file_" + identifier, "basic_info");
            if (basicInfoIsExist == false)
            {
                var partDic = await _minioAPI.MultUploadByStreamAsync(bucketName, identifier, totalSize, null, "application/octet-stream", null);
                var fileModel = new FileModel();
                fileModel.UploadId = partDic["uploadId"].ToString();
                fileModel.Parts = (Part[])partDic["parts"];
                fileModel.Md5 = identifier;
                fileModel.Size = totalSize;
                fileModel.FileName = fileName;
                await _redisHelper.HashSetAsync("file_" + identifier, "basic_info", fileModel);
                return new int[] { };
            }
            List<string> keys = await _redisHelper.HashKeysAsync<string>("file_" + identifier + "_part");
            return keys.Select(s => Convert.ToInt32(s)).ToArray();
        }

        /// <summary>
        /// 上载分片
        /// </summary>
        /// <returns>是否需要合并</returns>
        public async Task UploadPartAsync(MinioFilePartUpload partData)
        {
            string md5 = partData.Identifier;
            int partNumber = partData.ChunkNumber;
            FileModel fileModel = await _redisHelper.HashGeAsync<FileModel>("file_" + md5, "basic_info");
            if (fileModel == null) // 为空异常（可能传完了）
                throw new Exception("上传出现异常");
            // 加锁上传（）
            string key = "file_" + md5 + "_part";
            string strPartNumber = partNumber.ToString();

            string lockKey = "lock_" + key + "_" + strPartNumber;
            string locVlue = strPartNumber;
            try
            {
                if (_redisHelper.LockTake(lockKey, locVlue, 20))
                {
                    Console.WriteLine(locVlue + ":上传开始");
                    var isExist = await _redisHelper.HashExistsAsync(key, strPartNumber);
                    if (isExist == false)
                    {
                        long size = partData.UpFile.Length;
                        Part[] parts = await _minioAPI.MultUploadByStreamAsync(fileModel.UploadId, bucketName, md5, partData.UpFile.OpenReadStream(), size, fileModel.Parts, partData.ChunkNumber);
                        string etag = parts[partNumber - 1].ETag;
                        if (string.IsNullOrEmpty(etag))
                            throw new Exception("获取文件etag失败");
                        _ = await _redisHelper.HashSetAsync(key, strPartNumber, etag);
                        await _redisHelper.HashIncrementAsync("file_" + md5, "part_count", 1);
                    }
                }
            }
            finally
            {
                _redisHelper.LockRelease(lockKey, locVlue);
            }
        }

        /// <summary>
        /// 分片合并
        /// </summary>
        /// <param name="md5"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public async Task Compose(FileCompose fileCompose)
        {
            string identifier = fileCompose.Identifier;
            string key = "file_" + identifier;
            var fileModel = await _redisHelper.HashGeAsync<FileModel>("file_" + identifier, "basic_info");
            if (fileModel == null) // 为空代表异常（可能传完了）
                throw new Exception("上传出现异常");
            try
            {
                if (!_redisHelper.LockTake("lock" + key, key, 20))
                {
                    Console.WriteLine("该锁已被使用");
                    return;
                }
                string cutStr = await _redisHelper.HashGeAsync(key, "part_count");
                string fCoutStr = fileModel.Parts.Length.ToString();
                while (cutStr != fCoutStr)
                {
                    await Task.Delay(200);
                    cutStr = await _redisHelper.HashGeAsync(key, "part_count");
                }

                using (MEDbContext db = new MEDbContext())
                {
                    // 查询数据库中是否已存入信息
                    var entity = db.Set<SystemFileBusinessEntity>().FirstOrDefault(x => x.BucketName == bucketName && x.ObjectName == identifier);
                    if (entity == null)
                    {
                        Part[] parts = new Part[fileModel.Parts.Length];
                        for (int i = 0; i < fileModel.Parts.Length; i++)
                        {
                            parts[i] = fileModel.Parts[i];
                            parts[i].ETag = await _redisHelper.HashGeAsync("file_" + identifier + "_part", fileModel.Parts[i].PartNumber.ToString());
                        }
                        await _minioAPI.CommitMultUploadAsync(fileModel.UploadId, bucketName, identifier, parts);
                        _redisHelper.KeyDelete(new string[] { "file_" + identifier, "file_" + identifier + "_part" }.ToList());
                    }
                    FileUpdate(identifier, fileModel.FileName, fileModel.Size, db);
                }
            }
            finally
            {
                _redisHelper.LockRelease("lock" + key, key);
            }
        }
    }
}
